//	COPYRIGHT (C) 1981 BY BOARD OF TRUSTEES,
//	LELAND STANFORD JUNIOR UNIVERSITY


let MSI() be $(msi
 /* 
	This function has to identify appropriate substructures in a given
    reference file corresponding to each different ion composition entered
    by the user, and to pass these substructures as "ALTERNATIVE" constraints
    to GLBLD.

	The basic loop is
	i) enter the composition of some ion observed in the spectrum
		of the unknown.
	ii) generate variant compositions allowing for additional
		H-transfers and neutral losses.
        iii) scan the library file of ion compositions for ions with
		correct compositions
		a) each such ion composition should be characterised
			by a number of different alternative ion constitutions,
			these get read in and built into an "ALTERNATIVE"
			constraint for GLBLD.
	iv) call GLBLD with the new alternative constraint.
        v) loop back to (i) (via temporary stop at GENOA # prompt)

	Of course, it isn't quite so simple for the call to GLBLD
     destroys current program. So, work by using a file (000MSF.CG) that
     holds info between calls to GLBLD. This holds data on number of
     alternatives, name of file for data-base etc.


	Mod, Dec 17 '79, intenisty constraints on ions.

	Mod, Dec 18-20 '79, methods for identifying substructures
      associated with ions of different itensity from that desired
      and specifying these as -ve constraints (i.e. NONE).
 

	MOD JAN 1980, relative scores with ions etc.

	LOSSES also now defined.


	Mod mid-Jan '80, (change in where intensity range held +
     set of support ions/losses given with each substructure).


	MOD LATE-JAN '80, CHANGED FILE-FORMATS

       MOD FEB '80, LOWRESMODE option

	MOD FEB '80, OTHERS mode


	MOD MAY '80, changes to how substructures with scores
        should get passed to GLBLD.



*/
static $(
ABANDONED = NIL // TRUE/FALSE flag, TRUE if processing abandoned before
		// work arrays allocated etc.
ALTLIST = NIL // List of names of all ion-constitutions found as
		// alternative "explanations" of some observed ion
ATNAMES = NIL // Vector containing (pointers to) names of atoms in
		// composition of unknown
COMPOSITIONS = NIL // Vector of subvectors, each subvector is
                // a composition list for one of variants
                // of ion currently taken from unknown
                // spectrum
COPY  = NIL  // TRUE-FALSE flag saying if all input should be copied to
		// file COPYF, this will be TRUE the first time
		// we enter MSI and have to create a file containing
		// common parameters to be used on all subsequent entries
COPYF = NIL	// File pointer used when creating file 000MSF.CG
GIVENION = NIL // Copy of given compostion.
GIVENMASS = NIL // (its nominal mass)
INTENSVAL = NIL // Intensity value for observed ion; -1 if no intensity restriction
IONLOW  = NIL // Low limit on intensity values for Ion retreived from Database
IONHIH  = NIL // High limit on intensity value for Ion retreived from Database
IONSCORES = NIL
IONSCORE = NIL
LOWRESMODE = NIL // Flag indicates if ONLY want m/z values used
MASSES = NIL // Vector containing nominal mass associated with each variant on
             // currently being considered from unknown's spectrum.
MAXMASS = NIL
MINMASS = NIL
MINOTHERINT = NIL // If in "OTHERS" mode, checking for ions predicted by
MINOTHERMASS = NIL // entries in data-base but not used, then these hold
		// minimum mass and intensity of interest.
MAXOTHERMASS = NIL // (ALLOW USER TO GIVE COMPLETE RANGE)
MAZ = NIL    // Nominal masses (integer) for each of different atom
	     // types, (normally use mass of lowest mass isomer if
             // dealing with things like chlorine etc).
MF = NIL     // (used when running along MOLFORM list in various different functions
MOLION = NIL
MODE = NIL
NEXTMASS = NIL // next mass value in data-base
NTYPES = NIL // The number of different atom types in the molecular
             // composition of the unknown.
NUM = NIL // Vector giving composition of ion read from reference file
NUMVARIANTS = NIL // Number of variants, due to H-transfers
		// additional neutral losses that should be
	        // generated from each ion composition entered by user.
OIN = NIL 	// OIN and OOUT used to hold pointers to I/O streams
OOUT = NIL 	// when reading/writing to some temporary file.
SCOREDLIST = NIL // List of (name . score) entries.
SC2  = NIL	// File pointer for temporary file
SC3  = NIL	// File pointer for temporary file
QQSTR = "MSI-HELP" // Response to ?? entered by user, (eventually this
        	// is intended to be a key into a real "HELP" file.
USEDIONLIZ = NIL // List of ion compositions found on HISTORY list.
VARIANTCMPS = NIL // Vector of subvectors each 
		// giving the composition difference between
		// the observed ion composition and an allowed
		// variant.
$)

let GETSCORE(STR) = valof $(gtscr
 static $( VAL = NIL $)
lp:
 OUTS(STR); OUTS(", score : ");
 LINEIN("");
 if NEXTIS(EOLTYPE) then resultis 0
 unless NEXTIS(NUMTYPE) do $( OUTS("I was expecting an integer.*C*L"); 
	FLUSHLINE(); goto lp $)
 VAL:=LOPITEM()
 unless (-CFMAX<VAL) & (VAL<CFMAX) do $(
	OUTS("Scores must be in range "); OUTNOS(-CFMAX+1); OUTNOL(CFMAX-1);
	FLUSHLINE()
	goto lp
	$)
 VAL:=REVISESCORE(NUMOFSTR(STR),VAL)
 resultis VAL
$)gtscr


let GETCMP(MSG) = valof $(gtcmp
  /* GETCMP unpacks the composition of one ion, or of one possible
     neutral loss, read by calling routine using LINEIN. Returns true
     if data is valid, otherwise false. 
     "MSG" is TRUE if should print warning messages etc
   */

  static $( ATNAME = NIL; DTYPE = NIL $);

  for a=1 to NTYPES do NUM!a:=0;
atom:   
  unless NEXTIS(STRTYPE) do $(bad
      unless MSG do resultis FALSE
      OUTS("*C*LAtom name missing.*C*L");         
      FLUSHLINE();
      resultis FALSE
     $)bad
  ATNAME:=LOPITEM();
  DTYPE:=DEFTYPEOF(ATNAME);
  if (DTYPE = 0) | (NOT (STREQUAL("ATOM",STROFNUM(DTYPE)))) then $(bad
         unless MSG do resultis FALSE
         OUTSNUM(ATNAME);
         OUTS(" is not an atom.*C*L");
         FLUSHLINE();
         resultis FALSE
        $)bad
  for A=1 to NTYPES do
      if ATNAME = ATNAMES!A then $(found
               NUM!A+:=(NEXTIS(NUMTYPE)->LOPITEM(),1);
               goto done
               $)found

  unless MSG do resultis FALSE
  OUTS(ATNAME);
  OUTS(" is not part of molecular composition!*C*L");
  FLUSHLINE()
  resultis FALSE;


done: 
     if NEXTIS(PSEOLTYPE) | NEXTIS(EOLTYPE) then resultis TRUE;
     goto atom
 $)gtcmp
let GENNULL() be $(GNNLL
/* May wish to allow a null alternative when not certain that any of the
 ion structures found for some observed ion is actually correct.
 The null substructure  will consist of a single atom, other than
 hydrogen, taken from the molecular formula.
 The size of the substructure is 1.
 Nothing is specified about bonding or Hrange constraints.
*/
static $( ATOMNAME = NIL; NULLSCORE = NIL $);

NULLSCORE:=GETSCORE("None-of-these");
if NULLSCORE=0 then return;
NULLSCORE:=CFMAX+NULLSCORE

CONLENGTHS:=CONS(1,CONLENGTHS)
ATOMNAME:=NUMOFSTR("None-of-these")
SCOREDLIST:=CONS(CONS(ATOMNAME,NULLSCORE),SCOREDLIST);

/* Choose atom name */
for A=1 to NTYPES do
  if ATNAMES!A NE HSTRNUM then $( ATOMNAME:=ATNAMES!A; break $)

OOUT:=OUTPUT
OUTPUT:=SC2
/* Substructure should be written in format
  i) atom name, ii) H-range (0 VALENCE), iii) aromaticity (3=either) HYBRIDTYPE (15=ANY) 
 iv) nbr list (empty) v) terminating 0 for nbr list
 vi) number of LNODES (0), vii) Proton constraint? (N)
viii) range 1 100


 ix) SCORE 

*/
OUTSNUM(ATOMNAME);
OUTS(" 0 ");OUTNOS(VALENCE(ATOMNAME)); 
OUTS("3 15 4095 0*C*L0*C*LN*C*L1*C*L100*C*L")
OUTNOL(NULLSCORE-CFMAX)
OUTPUT:=OOUT
$)GNNLL
let INTENSRANGE(OUTOK) be $(
 /* OUTOK is false if not to echo input. */
  static $( LOWINT = NIL; HIGHINT = NIL $)
 LOWINT:=GETPOSINT("","",FALSE); HIGHINT:=GETPOSINT("","",FALSE);
 unless OUTOK do return
 OUTNON(LOWINT,4); OUTS(" - ");
 OUTNON(HIGHINT,4); OUTS("%*C*L")
 $)


let SUPPORT(OUTOK) be $(
  /* Sometimes called with OUTOK false when just want to skip this
 data without listing it, otherwise OUTOK is TRUE and data to be listed.
  */
 static $( NAME = NIL; NAMELEN = NIL; MASS = NIL; NUMPKS = NIL  $)
 NUMPKS:=GETNONNEGINT("","",FALSE);
 FOR I=1 TO NUMPKS do
 $(
  LINEIN("");
  MASS:=LOPITEM()
  if OUTOK then $( SPACES(10); 
	  OUTNON(MASS,6); /* m/z value */
	  SPACES(1)
	  $)
  NAME:=LOPITEM(); 
  if OUTOK then $(lst
	  NAMELEN:=NCHARS(STROFNUM(NAME));
	  test LOWRESMODE then SPACES(24)
	   or   $( 
		OUTSNUM(NAME); 
		test NAMELEN>23 then $( NEWLINE(1); SPACES(41) $)
		or SPACES(24-NAMELEN) $)
	$)lst
 INTENSRANGE(OUTOK)
 $)
$)


//  let SAVONE(NAME) be $(svn
//   /* Current substructure is to be put in "TOP" file
//   */
//   static $( OIN = NIL; OOUT = NIL; ESSIX = NIL; CHUNKIX = NIL; NCOPY = NIL $)
// 
// 
//  OIN:=INPUT;
//  INPUT:=FINDFILE("DSK",TOPFILENAME(),CGEXT);
//  CHUNKIX:=FINDSEG(CHUNKSEP,ESHEADSTR);
//  ESSIX:=(CHUNKIX>0 -> FINDSEG(ESSEP,STROFNUM(NAME)),0);
//  ENDREAD(INPUT);
//  INPUT:=OIN
// 
//  OIN:=INPUT; OOUT:=OUTPUT;
//  INPUT:=FINDFILE("DSK",TOPFILENAME(),CGEXT);
//  OUTPUT:=CREATEFILE("DSK",SC3FILENAME(),CGEXT);
//  
//  NCOPY:=(CHUNKIX>0 -> CHUNKIX, -CHUNKIX+1);
//  WHILE NCOPY>0 DO $( NCOPY-:=1; COPYSEG(CHUNKSEP,TRUE) $);
//  TEST CHUNKIX>0 THEN
//   $(
//   NCOPY:=(ESSIX>0 -> ESSIX,-ESSIX+1);
//   WHILE NCOPY>0 DO $( NCOPY-:=1; COPYSEG(ESSEP,TRUE) $);
//   IF ESSIX>0 DO SKIPSEG(ESSEP);
//   OUTSNUM(NAME);
//   NEWLINE(1);
//   OUTS("SUBSTRUCTURE")
//   NEWLINE(1);
//   WRITEESSTRUC();
//   OUTCH(ESSEP)
//   $)
//  OR
//   $(
//   OUTS(ESHEADSTR);
//   NEWLINE(1);
//   OUTCH(ESSEP);
//   OUTSNUM(NAME);
//   NEWLINE(1);
//   OUTS("SUBSTRUCTURE")
//   NEWLINE(1);
//   WRITEESSTRUC();
//   OUTCH(ESSEP);
//   NEWLINE(1);
//   OUTCH(CHUNKSEP)
//   $);
//  COPYTOEND();
//  ENDREAD(INPUT);
//  ENDWRITE(OUTPUT);
//  INPUT:=OIN
//  OUTPUT:=OOUT
//  INTERRUPTABLE(FALSE);
//  FILEREPLACE(TOPFILENAME(),CGEXT,SC3FILENAME(),CGEXT);
//  INTERRUPTABLE(TRUE)
//  $)svn
// 

let PROCESSION() be $(PRN
 /* Ion/loss described by current segment in data file appears
relevant to one of ions observed for the unknown compound.
Find out how many different possible constitutions have been
defined for an ion/loss at this mass with this composition.
Get names associated with these. 

Output Composition,
List the names, and intensities.
*/
static $( NAME = NIL; NAMELEN = NIL $)
/* Read names of all substructures given for this ion/loss composition,
 should occur one per line.
 Terminated by a blank line.
*/


LINEIN("")

test LOWRESMODE then 
	if ALTLIST=@NULL do OUTS("Possible substructures:*C*L") 
or $(cmp
  OUTS("Possible substructures for composition : ")
  for N=1 to NTYPES do 
	if NUM!N GR 0 then $( 
		OUTSNUM(ATNAMES!N); 
		OUTNO(NUM!N) $)
$)cmp

/* PRINT EXPLANATION OF "SUPPORT" OUTPUT ON FIRST EXAMPLE. */
if ALTLIST=@NULL do OUTS("*C*Ltogether with their typical ion-patterns");

until NEXTIS(EOLTYPE) do $(
  NAME:=LOPITEM()
  NEWLINE(1);
  OUTSNUM(NAME); NAMELEN:=NCHARS(STROFNUM(NAME));
  test NAMELEN>40 then $( NEWLINE(1); SPACES(41) $)
  or SPACES(41-NAMELEN)
  INTENSRANGE(TRUE);
  SUPPORT(TRUE);

  LINEIN("")


  ALTLIST:=CONS(NAME,ALTLIST)


 $)

SKIPSEG(CHUNKSEP)

$)PRN


let IONLOSSSUB(NAME,SUBSCORE) = valof $(NLSB

 static $( XIN = NIL $);

 test (ASSOC(NAME,ALLDEFS)=@NULL) then $(new
	 XIN:=INPUT;
	 INPUT:=FINDFILE("DSK",PROMPTFILENAME,PROMPTFILEEXT);

	 FINDSEG(CHUNKSEP,ESHEADSTR);
	
	 FINDSEG(ESSEP,STROFNUM(NAME))
	 SKIPSEG('*L')
	 READESSTRUC()
	 ENDREAD(INPUT);
	 INPUT:=XIN
	 unless CONSTRAINTCHECK(TRUE,NAME,FALSE) do $( CLEAR(); resultis FALSE $)
         OOUT:=OUTPUT; OUTPUT:=SC3
	 OUTSNUM(NAME)
	 NEWLINE(1)
	 OUTS("SUBSTRUCTURE")
	 NEWLINE(1)
	 WRITEESSTRUC(); 
	 OUTCH(ESSEP)
	 OUTPUT:=OOUT
	$)new
  or $(current
     FETCHSUB(NAME)
     unless CONSTRAINTCHECK(TRUE,NAME,FALSE) do $( CLEAR(); resultis FALSE $)
     $)current

 CONLENGTHS:=CONS(LENGTH(CTELIST),CONLENGTHS)
 OOUT:=OUTPUT;OUTPUT:=SC2;
 PASSESSTRUCOUT()
 OUTS("N*C*L1*C*L100*C*L");
 OUTNOL(SUBSCORE)
 OUTPUT:=OOUT; 
 CLEAR()
 resultis TRUE

$)NLSB


let SELECTANDSCORE() be $(slctscr
 STATIC $( OIN = NIL; OOUT = NIL; SUBNAME = NIL; SUBSCORE = NIL; ENTRY = NIL $)
 /* Run down the entries on ALTLIST, get user to specify relative
  scores in range -CFMAX<SCORE<CFMAX
  Scores of zero imply substructure is to be ignored.
  Build a list, SCOREDLIST, containing names and scores.
  If structures are being used, copy them from the "TOP" file.
 */
 SC3:=CREATEFILE("DSK",SC3FILENAME(),CGEXT);
 OIN:=INPUT; OOUT:=OUTPUT;
 INPUT:=FINDFILE("DSK",TOPFILENAME(),CGEXT);
 OUTPUT:=SC3
 COPYSEGSTO(CHUNKSEP,ESHEADSTR,TRUE);
 $(RPT
  COPYSEG(ESSEP,TRUE);
  SUBNAME:=INS0(TRUE);
  if NCHARS(SUBNAME)=0 then break
  OUTS(SUBNAME)
  NEWLINE(1)
 $)RPT REPEAT
 ENDREAD(INPUT)
 INPUT:=OIN
 OUTPUT:=OOUT
 SCOREDLIST:=@NULL
 OUTS("Assign relative confidence ratings (SCORES) to the different*C*L");
 OUTS("substructures. (SCORE 0 => ignore substructure)*C*L");
 $(rpt
  SUBNAME:=CAR(ALTLIST);

  ALTLIST:=UNCONS(ALTLIST);


  SUBSCORE:=GETSCORE(STROFNUM(SUBNAME));
  if SUBSCORE=0 then loop

  unless IONLOSSSUB(SUBNAME,SUBSCORE) do loop;

  /* Have minor silly here, can't store -ve numbers in lists, so
    scores are saved offset wrt CFMAX.
  */
  SUBSCORE:=CFMAX+SUBSCORE
  ENTRY:=CONS(SUBNAME,SUBSCORE);
  SCOREDLIST:=CONS(ENTRY,SCOREDLIST)

 $)rpt repeatuntil ALTLIST=@NULL
 GENNULL();
 OIN:=INPUT;
 INPUT:=FINDFILE("DSK",TOPFILENAME(),CGEXT)
 OOUT:=OUTPUT
 OUTPUT:=SC3
 NEWLINE(1)
 FINDSEG(CHUNKSEP,ESHEADSTR)
 SKIPSEG(CHUNKSEP)
 OUTCH(CHUNKSEP)
 COPYTOEND()
 ENDWRITE(SC3)
 OUTPUT:=OOUT
 INTERRUPTABLE(FALSE);
 FILEREPLACE(TOPFILENAME(),CGEXT,SC3FILENAME(),CGEXT)
 INTERRUPTABLE(TRUE)
 SCOREDLIST:=DREVERSE(SCOREDLIST)



$)slctscr


let FINDIONS() BE $(FNNS


 OIN:=INPUT;
 INPUT:=FINDFILE("DSK",PROMPTFILENAME,PROMPTFILEEXT)
 SWAPLITEMS()

LP:
 FINDSEG(ESSEP,MODE); 

 /* READ NEXT MASS FROM THE REFERENCE FILE */
 LINEIN("")
 NEXTMASS:=GETNONNEGINT("",QQSTR,FALSE)

 /* File will be terminated by a ZERO mass. */

 if NEXTMASS LE 0 then goto DONE


 if NEXTMASS LS MINMASS then $( SKIPSEG(CHUNKSEP); goto LP $);
 if NEXTMASS GR MAXMASS then goto DONE

 /* See if this mass corresponds to any of the ion/loss variants being
 considered.
 */

 for I=0 to NUMVARIANTS do 
    if MASSES!I=NEXTMASS then goto OKMASS;

 /* If this mass does not correspond to anything that we are
 looking for then can throw away the rest of this segment of the
 data file
 */
 SKIPSEG(CHUNKSEP)
 goto LP

OKMASS:
 /* The mass seems OK, read in the composition. */
 LINEIN("")
 unless GETCMP(FALSE) do $(
	/* Something wrong with this ion, its using atom types irrelevant
	to current problem or some similar fault. Ignore it.
         */
        SKIPSEG(CHUNKSEP)
        goto LP
	$)

/* If in low res mode, just accept this ion as mass OK */

if LOWRESMODE then $(lowok
	PROCESSION(); goto LP

	$)lowok

 /* Find if the composition matches  any of the ion/loss variants */

 for I=0 to NUMVARIANTS do $(CMPCHK
	static $( OKCOMP = NIL $)
	OKCOMP:=TRUE
	for A=1 to NTYPES do
                 OKCOMP:=OKCOMP & (NUM!A=[COMPOSITIONS!I]!A);
          if OKCOMP then $( PROCESSION();  goto LP $)
	$)CMPCHK

/* If this ion/loss composition doesn't match any of the
allowed variants then throw away the rest of the data
in this segment of the file.
*/
  SKIPSEG(CHUNKSEP)
  goto LP



DONE:
 ENDREAD(INPUT)
 SWAPLITEMS()
 INPUT:=OIN
$)FNNS

let GETMASSES() = valof $(gtms
 static $( NT = NIL $);
   let ATMASS(A) = valof $(mass
      static $( NMASS = NIL $)
      for n=1 to 4 do
        if STREQUAL(STROFNUM(A),[TABLE 0,"C","N","O","H"]!n) then
           resultis [TABLE 0,12,14,16,1]!n;
      if COPY then $(prompt
      	OUTS("Atom type ");
	OUTSNUM(A);
	OUTS(" is new.")
	$)prompt

      NMASS:=GETPOSINT("*C*LIntegral mass : ",QQSTR,FALSE) 
      if COPY then $( OOUT:=OUTPUT; OUTPUT:=COPYF; OUTNOL(NMASS);
		      OUTPUT:=OOUT $)

      resultis NMASS
      $)mass

    MF:=MOLFORM;
    NT:=0;
    while MF NE @NULL do $(lp
       static $( ATN = NIL; ATV = NIL; ATT = NIL; ATQ = NIL $)
       ATT:=CAR(MF);
       ATN:=CAR(ATT); ATQ:=CDR(ATT)
       ATT:=DEFTYPEOF(ATN);
       unless (ATT NE 0) & STREQUAL("ATOM",STROFNUM(ATT)) do
	$( OUTS("*C*LThe MSA functions only handle fully imbeded structures.");
	   OUTSNUM(ATN); OUTS(" is not an atom.");
           resultis false
         $);
       NT+:=1;
       ATNAMES!NT:=ATN;
       MAZ!NT:=ATMASS(ATN);
       MOLION!NT:=ATQ
       MF:=CDR(MF)
       $)lp ;
    resultis TRUE
    $)gtms

let GETION() = valof $(gtn

lp:
test LOWRESMODE then $(
 GIVENMASS:=GETPOSINT("M/Z : ","M/Z value for ion",FALSE);
 for N=1 to NTYPES do NUM!N:=0;
 $)
or $(cmps
 unless CONDPROMPT("Composition: ",0,
	[TABLE 2,"A list of atom names and numbers separated by spaces or",	
	"commas, 1s may be omitted"],QQSTR,STV)
    do resultis FALSE
 unless GETCMP(TRUE) do goto lp
 $)cmps

/* INTENSVAL is allowed to take -ve value if nothing specified. */

INTENSVAL:=GETPOSINT("Observed Intensity : ",QQSTR,FALSE)
resultis TRUE
$)gtn

let GENERATEVARIANTS() be $(gv
/* Composition of an ion observed in the spectrum is currently in
vector NUM. Copy this into composition vector for variant 0, then
create all variants with different H-transfers and neutral losses.
*/
static $( NOMMASS = NIL $)



for N=1 to NTYPES do GIVENION!N,[COMPOSITIONS!0]!N:=NUM!N,NUM!N

unless STREQUAL(MODE,"ION") do $(loss
	test YESNO("Molecular Ion as Parent?","HELP","YES")
	    then for N=1 to NTYPES do NUM!N:=MOLION!N
	    or $( 
lpp:
		until CONDPROMPT("Parent Ion Composition: ",0,
		[TABLE 2,"A list of atom names and numbers separated by spaces or",	
		"commas, 1s may be omitted"],QQSTR,STV)
		    do  FLUSHLINE();
		unless GETCMP(TRUE) do goto lpp
		  $)
        for N=1 to NTYPES do [COMPOSITIONS!0]!N:= NUM!N-[COMPOSITIONS!0]!N; 
	$)loss


for I=1 to NUMVARIANTS do $(vary
	for N=1 to NTYPES do
		[COMPOSITIONS!I]!N:=[COMPOSITIONS!0]!N-[VARIANTCMPS!I]!N
	$)vary


/* Fill in vector giving nominal masses. */
MINMASS:=PLUSINF;MAXMASS:=0
for I=0 to NUMVARIANTS do $(mass
	NOMMASS:=0;
	for N=1 to NTYPES do
		NOMMASS+:=(MAZ!N)*([COMPOSITIONS!I]!N)
	MASSES!I:=NOMMASS
	MINMASS:=(NOMMASS<MINMASS->NOMMASS,MINMASS)
	MAXMASS:=(NOMMASS>MAXMASS->NOMMASS,MAXMASS)
	$)mass

test LOWRESMODE then $(low
	test STREQUAL(MODE,"ION") then $(i
		for I=0 to NUMVARIANTS do MASSES!I+:=GIVENMASS
		MINMASS+:=GIVENMASS
		MAXMASS+:=GIVENMASS
		$)i
	or $(l
 		for I=0 to NUMVARIANTS do MASSES!I-:=GIVENMASS
		MINMASS-:=GIVENMASS
		MAXMASS-:=GIVENMASS
	    $)l
	$)low
or GIVENMASS:=MASSES!0

$)gv
let SETUP() = valof $(STP
/* We want to avoid requiring the user to redefine common parameters
 every time an additional ion is processed. Since we can't keep
 data in memory, (everything being lost after a call to GLBLD) use
 a file.
*/
static $( ALLOK = NIL $)
static $( STR1 = NIL; STR2 = NIL; STR3 = NIL; STR4 = NIL $)
static $(
STRA = "Which is the file containing your Mass Spec Database : ";
STRB = "How many different variants do you want generated for each observed ion? ";
STRC = "Composition of neutral loss or H-transfer : ";
STRD = "Low res data ? : " 
$)

 LET READPARMS() BE $(RDP
static  $( FILESTR = NIL $)

 ABANDONED:=TRUE;

TRYPROMPT:
  unless CONDPROMPT(STR1,
	"(enter the file-name of existing file)",0,QQSTR,STV)
	do $( ALLOK:=FALSE; return $)
  FILESTR:=STROFNUM(LITEMS![LPOSN+1]);
  unless UNPACKFILENAME(FILESTR) do
   $( OUTS("Incorrect format for file-name.*C*L"); FLUSHLINE(); goto TRYPROMPT $)

  LOPITEM()
  unless FILEEXISTS(PROMPTFILENAME,PROMPTFILEEXT) do $(
    OUTS("Can't find that file.*C*L")
    goto TRYPROMPT
    $)

  if COPY then $( OOUT:=OUTPUT; OUTPUT:=COPYF; OUTS(FILESTR); NEWLINE(1); OUTPUT:=OOUT $)


  unless GETMASSES() do $( ALLOK:=FALSE; return $)


  if CONDPROMPT(STR2,0,
       [TABLE 2,"Each variant on the observed composition will be generated using*C*L",
                "additional H-transfers and neutral losses.*C*L"],
	QQSTR,NTV) then NUMVARIANTS:=LOPITEM()
  if NUMVARIANTS LS 0 then NUMVARIANTS:=0

  if COPY then $( OOUT:=OUTPUT; OUTPUT:=COPYF; OUTNOL(NUMVARIANTS);
		  OUTPUT:=OOUT $)

  ABANDONED:=FALSE;

  MASSES:=NEWVEC(NUMVARIANTS);
  COMPOSITIONS:=NEWVEC(NUMVARIANTS);
  VARIANTCMPS:=NEWVEC(NUMVARIANTS);

  for I=0 to NUMVARIANTS do $(
	VARIANTCMPS!I:=NEWVEC(NTYPES)
	COMPOSITIONS!I:=NEWVEC(NTYPES)
	$)

  /* Now get compositions for each variant. */
  for I=1 to NUMVARIANTS do $(Fillvariant
    unless CONDPROMPT(STR3,0,
	[TABLE 2,"a list of atom names and numbers, seperated by spaces or",
	"commas, 1s may be omitted."],QQSTR,STV)
	do $( FLUSHLINE(); ALLOK:=FALSE; return $)
    unless GETCMP(COPY) do $( ALLOK:=FALSE; return $)
    for N=1 to NTYPES do $(
	[VARIANTCMPS!I]!N:=NUM!N
	if (COPY & (NUM!N > 0)) then $( OOUT:=OUTPUT; OUTPUT:=COPYF
		OUTSNUM(ATNAMES!N); SPACES(1); OUTNOS(NUM!N); OUTPUT:=OOUT $)
	$)
    if COPY then $( OOUT:=OUTPUT; OUTPUT:=COPYF; NEWLINE(1); OUTPUT:=OOUT $)
    $)Fillvariant


  /* MODS FEB 80, LOWRESMODE */

  LOWRESMODE:=YESNO(STR4,
  " Low resolution, nominal mass, or high resolution,compositions, as data.","NO")
  if COPY then $( OOUT:=OUTPUT; OUTPUT:=COPYF;
	test LOWRESMODE then OUTS("YES*C*L") or OUTS("NO*C*L");
	OUTPUT:=OOUT
	$)


  $)RDP

ALLOK:=TRUE;
TEST (FILEEXISTS(MSFFILENAME(),CGEXT) &
   YESNO("Continue? ","Should we continue with existing parameters","YES")) then
 $( OIN:=INPUT; INPUT:=FINDFILE("DSK",MSFFILENAME(),CGEXT);
    SWAPLITEMS(); LITEMS!LPOSN:=EOLTYPE; COPY:=FALSE; 
    STR1:=""; STR2:=""; STR3:=""; STR4:=""; READPARMS(); SWAPLITEMS();
    ENDREAD(INPUT); 
    INPUT:=OIN
 $)
OR
 $( WHILE DELETEFILE(MSFFILENAME(),CGEXT) DO;
OUTS("*C*L--- Functions for Mass Spectral Interpretation ---*C*L*L");
    COPY:=TRUE; COPYF:=CREATEFILE("DSK",MSFFILENAME(),CGEXT);
    STR1:=STRA; STR2:=STRB; STR3:=STRC; STR4:=STRD; READPARMS();
    ENDWRITE(COPYF)
 $)
XIT:
resultis ALLOK
$)STP


LET COMPLETEGLBLD() BE $( 
 STATIC $( K = NIL; OOUT = NIL; STRVEC = VEC 25; NUMVEC = VEC 25 $)

 OOUT:=OUTPUT
 OUTPUT:=SC2
 COPYOLDONES(SCOREDLIST)
 MODMF:=@NULL;
 ENDWRITE(OUTPUT);

 test STREQUAL(MODE,"OTHERS") then 
	$( STRVEC!0,NUMVEC!0:=1,0; STRVEC!1:=NUMOFSTR("MISSING-IONS-FOR") $)
 or
 test LOWRESMODE then $( 
	STRVEC!0,NUMVEC!0:=1,1; 
	STRVEC!1:=NUMOFSTR("M/Z-"); 
	NUMVEC!1:=GIVENMASS
	$)
 or $(cmp
 /* Have to output the composition of the ion given. */
 /* (no spacing in output as want composition to be read as a single string.) */
 K:=0
 for N=1 to NTYPES do 
   if GIVENION!N GR 0 then 
	$( K+:=1; STRVEC!K:=ATNAMES!N; NUMVEC!K:=GIVENION!N $)
 STRVEC!0,NUMVEC!0:=K,K
 $)cmp


 REWORKFILES(4,SCOREDLIST,STRVEC,NUMVEC)
 OUTPUT:=OOUT
 
 MAPC(SCOREDLIST,UNCONS)
 UNLIST(SCOREDLIST)
 SCOREDLIST:=@NULL

 $);


let PR1() = valof $(pr1
retry:
  ALTLIST:=@NULL
  CONLENGTHS:=@NULL
  unless GETION() do  resultis FALSE
  
  GENERATEVARIANTS()


  FINDIONS()

  if ALTLIST=@NULL then $(
	OUTS("No relevant entries in the data-base.*C*L")
	if YESNO("Try another ? : ","Try another ion/loss","YES") then
		goto retry
	
	$)
  
  for I=0 to NUMVARIANTS do $( 
  	FREEVEC(VARIANTCMPS!I); 
  	FREEVEC(COMPOSITIONS!I) 
  	$)
  FREEVEC(VARIANTCMPS)
  FREEVEC(COMPOSITIONS)
  FREEVEC(MASSES)

  if ALTLIST=@NULL then resultis FALSE


  ALTLIST:=DREVERSE(ALTLIST)

  SC2:=CREATEFILE("DSK",SC2FILENAME(),CGEXT);
	
  SELECTANDSCORE()
  resultis TRUE

$)pr1


let USEDIONS() be $(sdns
/* have to get into TOP file, look at HISTORY segment, pull out a list
of all ion compositions used.
*/
static $( ITEM = NIL $);
USEDIONLIZ:=@NULL

OPENIN(TOPFILENAME);
FINDSEG(CHUNKSEP,HIHEADSTR);
LINEIN("");
IF NEXTIS(EOLTYPE) THEN 
  $(
  CLOSEIN();
  RETURN
  $);

UNTIL NEXTIS(EOLTYPE) DO $(LIST
  ITEM:=LOPITEM();
  TEST (ITEM=NUMOFSTR("ION-COMPOSITION"))  THEN 
    $(IONS
        USEDIONLIZ:=CONS(LOPITEM(),USEDIONLIZ)
        LINEIN("")
    $)IONS
  OR LINEIN("")
  $)LIST
 CLOSEIN()

//? OUTS("Used ions :*C*L");
//? ITEM:=USEDIONLIZ;
//? until ITEM=@NULL do $(
//?   OUTSNUM(CAR(ITEM)); NEWLINE(1); ITEM:=CDR(ITEM)
//?   $)


$)sdns


let USEDONE() = valof $(dn
/* Need to determine if the current ion as defind by NEXTMASS and entries
in NUM, corresponds to an ion thats is on the list USEDIONLIZ. 
Have to build a name, for this ion, in a vector, pack it into a string,
look for that string amongst those on USEDIONLIZ.
*/
static $( PTR = NIL; NAME = NIL; NUMCH = NIL; VECREP = vec 40; STRREP = vec 10 $)

 let PUTNUM(N) be $(ptnm
	static $( NC = NIL; VAL = NIL $)
	NC:=NCHARSN(N)
	NUMCH+:=NC;
	NC:=0;
	while N>0 do $( VECREP!(NUMCH-NC):=(N REM 10)+'0'; N:=N/10; NC+:=1 $)
	$)ptnm

  let PUTATN(N) be $(pttn
	/* Have to get copy of string representing atom 'N', this
	could be a multicharacter string.
	*/
	static $( COPYSTRNM = vec 20 $)
	UNPACKSTRING(STROFNUM(ATNAMES!N),COPYSTRNM);
	for I=1 to COPYSTRNM!0 do $( NUMCH+:=1; VECREP!NUMCH:=COPYSTRNM!I $)
	$)pttn

NUMCH:=0;
test LOWRESMODE then $(low
	VECREP!1:='M';VECREP!2:='/';VECREP!3:='Z';NUMCH:=3;
	PUTNUM(NEXTMASS)
	$)low
or $(hi
        for N=1 to NTYPES do
		if NUM!N>0 do $( PUTATN(N); PUTNUM(NUM!N) $)
	$)hi
VECREP!0:=NUMCH

/* Have now got characters into the array VECREP. Use PACKSTRING() to
put into STRREP.
*/
PACKSTRING(VECREP,STRREP);

//? OUTS("Generate "); OUTS(STRREP); NEWLINE(1)

/* now look through Names in USEDIONLIZ */

PTR:=USEDIONLIZ;
until PTR=@NULL do $(chk
	NAME:=CAR(PTR); PTR:=CDR(PTR);
	if STREQUAL(STRREP,STROFNUM(NAME)) then resultis TRUE
	$)chk
resultis FALSE

$)dn

let CHKADD() be $(
/* Current Ion seems to have a mass in range, and a meaningful composition,
get at associated substructures, intensities etc.
*/
static $( NAME = NIL; NAMELEN = NIL; LOWINT = NIL; HIGHINT = NIL $)

LINEIN("")
until NEXTIS(EOLTYPE) do $(
  NAME:=LOPITEM();
  LOWINT:=GETPOSINT("","",FALSE); HIGHINT:=GETPOSINT("","",FALSE);
  test  LOWINT LS MINOTHERINT then $(small
	/* Ignore this one, this substructure can give ions
	with intensities below minimum requested.
	have to skip over trailing data, (the set of support ions etc).
	*/
	SUPPORT(FALSE)
	$)small
  or $(ok
	if ALTLIST=@NULL do OUTS("Substructures associated with ions not yet assigned to unknown:*C*L")
	ALTLIST:=CONS(NAME,ALTLIST);
	OUTSNUM(NAME);
	NEWLINE(1)
	SPACES(10); OUTNON(NEXTMASS,6); SPACES(1);
	for N=1 to NTYPES do 
		if NUM!N>0 do $( OUTSNUM(ATNAMES!N); OUTNO(NUM!N) $)
	NEWLINE(1); SPACE(41); OUTNON(LOWINT,4); OUTS(" - ");
	OUTNON(HIGHINT,4); OUTS("%*C*L");
	SUPPORT(TRUE);
	NEWLINE(1)
     $)ok
  LINEIN("")
$)
SKIPSEG(CHUNKSEP)
$)


let PR2() = valof $(pr2
/* "OTHERS" mode. Object is to go through file identifying all
 ions, or losses leading to ions, at masses about MINOTHERMASS
 with minimum intensity exceeding MINOTHERINT.
 Report these.
 Put on ALTLIST for user to score if appropriate.
*/

  static $( MOLMASS = NIL; CHECKMASS = NIL $)

  let CHECKCOMP() = valof $(
	for N=1 to NTYPES do if NUM!N>MOLION!N then resultis FALSE
	resultis TRUE
	$)

  USEDIONS()
  if USEDIONLIZ=@NULL do $( OUTS("No ions used yet!*C*L"); UNLIST(USEDIONLIZ); resultis FALSE $)

  MINOTHERMASS:=GETPOSINT("Minimum Mass : ",
	"Minimum mass for predicted but unobserved ion",FALSE);
  unless MINOTHERMASS>0 do resultis FALSE;
  MAXOTHERMASS:=GETPOSINT("Maximum Mass : ",
	"Maximum mass for predicted but unobserved ion",FALSE);
  unless MAXOTHERMASS GE MINOTHERMASS do resultis FALSE

  MINOTHERINT:=GETPOSINT("Minimum intensity : ",
	"Minimum intensity for predicted but unobserved ion",FALSE);
  unless ((MINOTHERINT>0) & (MINOTHERINT LE 100)) do resultis FALSE;

  MOLMASS:=0; for N=1 to NTYPES do MOLMASS+:=(MAZ!N)*(MOLION!N)
  ALTLIST:=@NULL
  CONLENGTHS:=@NULL
  

/* First, deal with losses. */
  OIN:=INPUT; INPUT:=FINDFILE("DSK",PROMPTFILENAME,PROMPTFILEEXT)
  SWAPLITEMS();
  OUTS("Processing data on neutral losses.*C*L")

LOSSLP:
  FINDSEG(ESSEP,"LOSS");
  LINEIN("");
  NEXTMASS:=GETNONNEGINT("",QQSTR,FALSE);
  if NEXTMASS=0 then goto IONL1;

  if ((MOLMASS-NEXTMASS)<MINOTHERMASS) then $( SKIPSEG(CHUNKSEP); goto LOSSLP $)
  if ((MOLMASS-NEXTMASS)>MAXOTHERMASS) then $( SKIPSEG(CHUNKSEP); goto LOSSLP $)
  LINEIN(""); 
  unless GETCMP(FALSE) do $( SKIPSEG(CHUNKSEP); goto LOSSLP $)
  unless CHECKCOMP() do $( SKIPSEG(CHUNKSEP); goto LOSSLP $)
  /* Convert the loss into an ion composition. */
  for N=1 to NTYPES do NUM!N:=MOLION!N-NUM!N;
  NEXTMASS:=MOLMASS-NEXTMASS
  if USEDONE() then $( SKIPSEG(CHUNKSEP); goto LOSSLP $)
  CHKADD()
  goto LOSSLP

  /* N.B. Relying on the file containing a "LOSS" section and a subsequent
 "ION" section. */
IONL1:
  OUTS("Processing data on IONS.*C*L");
IONLP:
  FINDSEG(ESSEP,"ION");
  LINEIN("");
  NEXTMASS:=GETNONNEGINT("",QQSTR,FALSE);
  if NEXTMASS=0 then goto DONE;

  if (NEXTMASS<MINOTHERMASS) then $( SKIPSEG(CHUNKSEP); goto IONLP $)
  if (NEXTMASS>MAXOTHERMASS) then goto DONE
  LINEIN(""); 
  unless GETCMP(FALSE) do $( SKIPSEG(CHUNKSEP); goto IONLP $)
  unless CHECKCOMP() do $( SKIPSEG(CHUNKSEP); goto IONLP $)
  if USEDONE() then $( SKIPSEG(CHUNKSEP); goto IONLP $)
  CHKADD()
  goto IONLP

DONE:
  ENDREAD(INPUT);
  SWAPLITEMS();
  INPUT:=OIN

  if ALTLIST=@NULL then $(
	OUTS("No relevant entries in the data-base.*C*L")
	resultis FALSE
	$)
  

  ALTLIST:=DREVERSE(ALTLIST)

  SC2:=CREATEFILE("DSK",SC2FILENAME(),CGEXT);
	
  SELECTANDSCORE()
  
  UNLIST(USEDIONLIZ)

  resultis TRUE

$)pr2


 IF MOLFORM=@NULL DO
   $(
   FLUSHLINE();
   OUTS( "I CAN'T APPLY ANY CONSTRAINTS ");
   OUTS("WITHOUT THE MOLECULAR FORMULA*C*L");
   RETURN
   $);


 
  SC2:=0;
  NTYPES:=0;
  MF:=MOLFORM;
  until MF=@NULL do $( NTYPES+:=1; MF:=CDR(MF) $);
  ATNAMES:=NEWVEC(NTYPES);
  MAZ:=NEWVEC(NTYPES);
  MOLION:=NEWVEC(NTYPES);
  GIVENION:=NEWVEC(NTYPES);
  NUM:=NEWVEC(NTYPES);

  MODMF:=MOLFORM
  unless SETUP() do goto CLEAN1

  switchon
   PROMPTSELECT("TYPE : ","ION LOSS OTHERS",0,
	"ION => process composition as ion, LOSS => compute loss from composition given",
	[TABLE 3,"ION",1,"LOSS",2, "OTHERS",3 ,0],
	FALSE)
  into $(sw
  case 1: MODE:="ION"; endcase;
  case 2: MODE:="LOSS"; endcase;
  case 3: MODE:="OTHERS"; endcase
  default: goto CLEAN1
  $)sw
  
  test STREQUAL("OTHERS",MODE) then unless PR2() do goto CLEAN1
  or unless PR1() do goto CLEAN1

  if SCOREDLIST=@NULL then $(
	OUTS("No relevant entries in the data-base.*C*L")
	ENDWRITE(SC2)
        DELETEFILE(SC2FILENAME(),CGEXT)
  	goto CLEAN1
	$)

  COMPLETEGLBLD()


  STARTCGPART1(DNDPPN,"GLBLD")
  SC2:=0

CLEAN1:


  FREEVEC(NUM);
  FREEVEC(GIVENION);
  FREEVEC(MOLION);
  FREEVEC(MAZ);
  FREEVEC(ATNAMES)

$)msi



